#include "dockercurloperator.h"

bool DockerCurlOperator::startContainer(string containerId,string& error) 
{
	//start container
	string result2 ;
	string urlstart = "http://localhost/v1.43/containers/"+containerId+"/start" ;
	CURL *curl3 = curl_easy_init();
	if( curl3==0 ) {
		error = "Error, DockerCurlOperator::startContainer curl init failed." ;
		return false ;
	}
	curl_easy_setopt(curl3, CURLOPT_URL, urlstart.c_str() ) ;
	curl_easy_setopt(curl3, CURLOPT_UNIX_SOCKET_PATH, "/var/run/docker.sock");
	curl_easy_setopt(curl3, CURLOPT_POST, 1L ); //POST mode.
	curl_easy_setopt(curl3, CURLOPT_WRITEFUNCTION, DockerCurlOperator::write_callback);
	curl_easy_setopt(curl3, CURLOPT_WRITEDATA, (void *)&result2);
	CURLcode res  = curl_easy_perform(curl3);
	curl_easy_cleanup(curl3);
	if(res != CURLE_OK)
	{
		error = string("Error, container start failed for ")+containerId+" : " + curl_easy_strerror(res);
		return false ;
	}
	return true ;
}

bool DockerCurlOperator::removeContainer(string containerId,bool force,string& error) 
{
	//remove container
	string optjsontext = "{\"forece\":" ;
	if( force ) {
		optjsontext+="true}" ;
	}else{
		optjsontext+="false}" ;
	}
	string result2 ;
	string urlstart = "http://localhost/v1.43/containers/"+containerId  ;
	CURL *curl2 = curl_easy_init();
	if( curl2==0 ) {
		error = "Error, DockerCurlOperator::removeContainer curl init failed." ;
		return false ;
	}
	curl_easy_setopt(curl2, CURLOPT_URL, urlstart.c_str() ) ;
	curl_easy_setopt(curl2, CURLOPT_UNIX_SOCKET_PATH, "/var/run/docker.sock");
	curl_easy_setopt(curl2, CURLOPT_CUSTOMREQUEST, "DELETE" ); // 
	curl_easy_setopt(curl2, CURLOPT_POSTFIELDS,  optjsontext.c_str() );
	curl_easy_setopt(curl2, CURLOPT_WRITEFUNCTION, DockerCurlOperator::write_callback);
	curl_easy_setopt(curl2, CURLOPT_WRITEDATA, (void *)&result2);
	CURLcode res  = curl_easy_perform(curl2);
	curl_easy_cleanup(curl2);
	if(res != CURLE_OK)
	{
		error = string("Error, remove container (")+containerId+") " + curl_easy_strerror(res) ;
		return false ;
	}
	//the remove operation does not return any text when success
	//if something is wrong, there is not CURLE_OK.
	return true ;
}

bool DockerCurlOperator::getContainerLog(string containerId,string& logtext,string& error ) 
{
	//log container
	string result2;
	string urlstart = "http://localhost/v1.43/containers/"+containerId+"/logs?stdout=1" ;
	CURL *curl4 = curl_easy_init();
	if( curl4==0 ) {
		error = "Error, DockerCurlOperator::getContainerLog curl init failed." ;
		return false ;
	}
	curl_easy_setopt(curl4, CURLOPT_URL, urlstart.c_str() ) ;
	curl_easy_setopt(curl4, CURLOPT_UNIX_SOCKET_PATH, "/var/run/docker.sock");
	curl_easy_setopt(curl4, CURLOPT_WRITEFUNCTION, DockerCurlOperator::write_callback);
	curl_easy_setopt(curl4, CURLOPT_WRITEDATA, (void *)&result2);
	CURLcode res  = curl_easy_perform(curl4);
	curl_easy_cleanup(curl4);
	if(res != CURLE_OK)
	{
		error = string("Error, get container log failed for ")+containerId+" : " + curl_easy_strerror(res);
		return false ;
	}
	logtext = result2 ;
	//cout<<"debug log "<<endl ;
	//cout<<logtext<<endl ;
	return true ;
}
bool DockerCurlOperator::waitContainer(string containerId,string& error) 
{
	//wait container
	string result2 ;
	string urlstart = "http://localhost/v1.43/containers/"+containerId+"/wait" ;
	CURL *curl3 = curl_easy_init();
	if( curl3==0 ) {
		error = "Error, DockerCurlOperator::waitContainer curl init failed." ;
		return false ;
	}
	curl_easy_setopt(curl3, CURLOPT_URL, urlstart.c_str() ) ;
	curl_easy_setopt(curl3, CURLOPT_UNIX_SOCKET_PATH, "/var/run/docker.sock");
	curl_easy_setopt(curl3, CURLOPT_POST, 1L ); //POST mode.
	curl_easy_setopt(curl3, CURLOPT_WRITEFUNCTION, DockerCurlOperator::write_callback);
	curl_easy_setopt(curl3, CURLOPT_WRITEDATA, (void *)&result2);
	CURLcode res  = curl_easy_perform(curl3);
	curl_easy_cleanup(curl3);
	if(res != CURLE_OK)
	{
		error = string("Error, container wait failed for ")+containerId+" : " + curl_easy_strerror(res);
		return false ;
	}
	//cout<<"debug wait "<<endl ;
	//cout<<result2<<endl ;
	return true ;
}
 
 
size_t DockerCurlOperator::write_callback(void *contents, size_t length, size_t nmemb,
                                  void *userp) {
	string* str = (string*)userp ;
	if( nmemb>0 ) {
		char* cptr = (char*) contents ;
		for(int i = 0 ; i<nmemb; ++ i ) str->push_back( *(cptr++) ) ;	
	}
}

bool DockerCurlOperator::isImageExists(string imageName ) {
	CURL *curl = curl_easy_init();
	if( curl==0 ) {
		cout<<"Error, DockerCurlOperator::isImageExists curl_easy_init failed."<<endl ;
		return false ;
	}
	string url = string("http://localhost/v1.43/images/") + imageName + "/json" ;
	string result_text ;
	curl_easy_setopt(curl, CURLOPT_URL, url.c_str() ) ;
 	curl_easy_setopt(curl, CURLOPT_UNIX_SOCKET_PATH, "/var/run/docker.sock");
 	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DockerCurlOperator::write_callback);
  	curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&result_text);
 	CURLcode res  = curl_easy_perform(curl);
 	curl_easy_cleanup(curl);
    if(res != CURLE_OK)
    {
    	cout<<"Error, curl perform failed: "
    	<<curl_easy_strerror(res)<<endl;
    	return false ;
    }
    //debug cout<<result_text<<endl ;
    size_t posId = result_text.find("Id") ;
    if( posId!=string::npos ) {
    	return true ;
    }else {
    	return false ;
    }
}

bool DockerCurlOperator::createContainer(
	string imageName,
	vector<DockerMount>& mounts,
	vector<string>& cmds,
	string& retContainerId,
	string& error) 
{
	CURL *curl = curl_easy_init();
	if(curl==0) {
		error = "Error, curl init failed." ;
		return false ;
	}
	string url = string("http://localhost/v1.43/containers/create")  ;
	string result_text ;
	DynamicJsonBuffer jsonBuffer ;
	JsonObject& jsonRoot = jsonBuffer.createObject() ;
	JsonArray& cmdArr = jsonRoot.createNestedArray("Cmd") ;
	for(int ic = 0 ; ic<cmds.size(); ++ ic ) {
		cmdArr.add( cmds[ic] ) ;
	}
	JsonObject& hostConfig = jsonRoot.createNestedObject("HostConfig") ;
	jsonRoot["Image"] = imageName ;
	jsonRoot["Tty"] = true;

	//mounts
	JsonArray& mountsArray = hostConfig.createNestedArray("Mounts") ;
	for(int im = 0 ; im<mounts.size(); ++ im ) {
		JsonObject& o1 = mountsArray.createNestedObject() ;
		o1["Target"] = mounts[im].targetdir ;
		o1["Source"] = mounts[im].sourcedir ;
		o1["Type"] = "bind" ;
		o1["ReadOnly"] = mounts[im].readOnly ;
	}

	//logs
	// JsonObject& logconfig  = hostConfig.createNestedObject("LogConfig") ;
	// logconfig["Type"] = "json-file" ;
	// JsonObject& logconfig2 = logconfig.createNestedObject("Config") ;
	// logconfig2["max-size"]="1m" ;
	// logconfig2["max-file"]="1";

	string jsontext ;
	jsonRoot.printTo(jsontext) ;

	//cout<<"debug jsontext:"<<endl ;
	//cout<<jsontext<<endl ;
	//cout<<endl ;

	curl_slist *optlist = nullptr;
	optlist = curl_slist_append(optlist,"Content-Type: application/json" );
	optlist = curl_slist_append(optlist,"Accept: application/json");

	curl_easy_setopt(curl, CURLOPT_URL, url.c_str() ) ;
 	curl_easy_setopt(curl, CURLOPT_UNIX_SOCKET_PATH, "/var/run/docker.sock");
 	curl_easy_setopt(curl, CURLOPT_POST, 1L ); //POST mode.
 	curl_easy_setopt(curl, CURLOPT_HTTPHEADER, optlist);
 	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsontext.c_str() );
 	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DockerCurlOperator::write_callback);
  	curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&result_text);

 	CURLcode res  = curl_easy_perform(curl); 
 	curl_slist_free_all(optlist); 
 	curl_easy_cleanup(curl);
    if(res != CURLE_OK)
    {
    	error = string("Error, create container failed for ")+imageName+" : " + curl_easy_strerror(res);
    	return false ;
    }
    // cout<<"debug runBashAndRemove output:"<<endl ;
    // cout<<result_text<<endl ;
    // cout<<endl ;

    JsonObject& createResultJson = jsonBuffer.parse(result_text.c_str()) ;
    if( createResultJson.success()==false ) {
    	error = "Error, failed to parse create container result json." ;
    	return false ;
    }
    if( createResultJson.containsKey("Id") ==false ) {
    	error = "Error, can not find container Id from create container result json, maybe create failed." ;
    	return false ;
    }
    retContainerId = createResultJson["Id"].as<char*>() ;

    //cout<<"debug create "<<endl ;
	//cout<<result_text<<endl ;
    return true ;
}

bool DockerCurlOperator::runCommandsAndRemove(string imageName,
	vector<DockerMount>& mounts, // 
	string containerlogpath,
	vector<string>& commands,
	string& error
	) 
{
	if( isImageExists(imageName) ==false ) {
		error = string("Error, DockerCurlOperator::runCommandsAndRemove ") + imageName + " is not exists." ;
		return false ;
	}
	string containerId  ;
	string error1 ;
	bool ok1 = createContainer(
		imageName, mounts, commands,containerId , error1 ) ;
	if( ok1==false ) {
		error = string("Error, DockerCurlOperator::runCommandsAndRemove createContainer failed imageName: ")
			+imageName + "\n"
			+error1 ;
		return false ;
	}
	//cout<<"containerId: "<<containerId<<endl ;

	ok1 = startContainer(
		 containerId , error1 ) ;
	if( ok1==false ) {
		error = string("Error, DockerCurlOperator::runCommandsAndRemove startContainer failed containerId: ")
			+containerId + "\n"
			+error1 ;
		return false ;
	}

	bool ok2 = waitContainer(containerId,error1) ;
	if( ok2==false ) {
		error = string("Error, DockerCurlOperator::runCommandsAndRemove waitContainer failed containerId: ")
			+containerId + "\n"
			+error1 ;
		return false ;
	}
	string logtext ;
	bool ok3 = getContainerLog(containerId,logtext,error1) ;
	if( ok3==false ) {
		error = string("Error, DockerCurlOperator::runCommandsAndRemove getContainerLog failed containerId: ")
			+containerId + "\n"
			+error1 ;
		return false ;
	}
	std::ofstream ofs( containerlogpath.c_str() ) ;
	if( ofs.is_open()==false ) {
		error = string("Error, DockerCurlOperator::runCommandsAndRemove failed to open log filename: ")
			+containerlogpath + "\n" ;
		return false ;
	}
	ofs<<logtext ;
	ofs.close() ;
  	//remove container forcelly
  	bool ok4 = removeContainer( containerId,true,error1) ;
  	if( ok4==false ) {
		error = string("Error, DockerCurlOperator::runCommandsAndRemove removeContainer failed containerId: ")
			+containerId + "\n"
			+error1 ;
  		return false ;
  	}
    return true ;
}